import numpy as np
import scipy.stats as scs
import statsmodels.api as sm
import matplotlib as mpl
import matplotlib.pylab as plt
import scipy.optimize as sco
import pandas as pd
from common.webHelper import *
import scipy.interpolate as sci
from scipy.interpolate import splev, splrep

class Portfolio:
    def __init__(self,portfolio_close_price_df,symbols,risk_free_rate=0.03):
        #symbols: array-like symbol
        #portfolio_close_price_df: DataFrame
        #                          columns=close price of each symbol in symobles;index = business day
        self.symbols = symbols
        self.data = portfolio_close_price_df
        self.daily_log_returns = np.log(self.data/self.data.shift(1))
        self.risk_free_rate = risk_free_rate

    def get_portfolio_statistics(self,weights):
        #Return Portfolio statistics with the given weights

        #Parameters:
        #====================
        #weights: array-like
        #       weights for different securities in the portfolio

        #Returns:
        #===============
        #pret: float
        #       expected portfolio return
        #pvol:float
        #       expected porfolio valatility
        #pret/pvol: float
        #       Sharp ratio for rf=0.

        weights = np.array(weights)
        pret = np.sum(self.daily_log_returns.mean() * weights)*252
        pvol = np.sqrt( np.dot(weights.T,np.dot(self.daily_log_returns.cov() * 252, weights )) )
        return np.array([pret,pvol,pret/pvol])

    def __min_func_sharpe(self, weights):
        return - self.get_portfolio_statistics(weights)[2]

    def __min_func_variance(self, weights):
        return self.get_portfolio_statistics(weights)[1] ** 2

    def __min_func_port(self, weights):
        return self.get_portfolio_statistics(weights)[1]

    def draw_monto_carlo_chart_with_weights(self,paths=2500,effective_rets=[]):
        "'draw vo-return distribution charts with different weights'"

        "':parameter'"
        #paths: how many paths to simulate
        prets=[]
        pvols=[]
        risk_free_rates_arr = np.array(self.risk_free_rate)
        print(risk_free_rates_arr)

        for p in range(paths):
            weights = np.random.random(len(self.symbols))
            weights/=weights.sum()
            rets = np.sum(self.daily_log_returns.mean()*weights) * 252
            vols = np.dot(weights.T,np.dot(self.daily_log_returns.cov()*252,weights))
            prets.append(rets)
            pvols.append(vols)

        plt.scatter(pvols,prets,c = ( prets - risk_free_rates_arr)/pvols, marker='o')
        plt.grid(True)
        plt.xlabel('expected vols')
        plt.ylabel('expected returns')
        plt.colorbar(label = 'sharp ratio')

        # if(len(effective_rets)>0):
        #     effective_vols =
        plt.show()


    def get_weight_with_maximum_sharp_given_vol(self):
        # return weights based for the given daily_log_returns

        #Parameters:
        #======================
        #daily_log_returns: array like daily log returns
        #num_of_assets: int
        #               how many assets in the portfolio

        #returns:
        #=================
        #array-like array of weights

        num_of_assets = len(self.symbols)
        initial_weights = []
        for i in range(0, num_of_assets):
            initial_weights.append(1. / num_of_assets)

        cons = ({'type':'eq','fun':lambda x:np.sum(x)-1})
        bnds = tuple((0,1) for x in range(num_of_assets))
        opts = sco.minimize(self.__min_func_sharpe,initial_weights,method='SLSQP',bounds=bnds,constraints=cons)

        print(np.round(opts.x,4))
        print(self.get_portfolio_statistics(opts.x))
        return opts.x

    def get_weight_with_minimum_vol_given_sharp(self):
        # return weights based for the given daily_log_returns

        #Parameters:
        #======================
        #daily_log_returns: array like daily log returns
        #num_of_assets: int
        #               how many assets in the portfolio

        #returns:
        #=================
        #array-like array of weights

        num_of_assets = len(self.symbols)
        initial_weights = []
        for i in range(0, num_of_assets):
            initial_weights.append(1. / num_of_assets)

        cons = ({'type':'eq','fun':lambda x:np.sum(x)-1})
        bnds = tuple((0,1) for x in range(num_of_assets))
        opts = sco.minimize(self.__min_func_variance,initial_weights,method='SLSQP',bounds=bnds,constraints=cons)
        print(opts)
        print(np.round(opts.x,4))
        print(self.get_portfolio_statistics(opts.x))
        return opts.x

    def get_vols_with_effective_returns(self,effective_returns):
        # return weights based for the given sharp ratio

        #Parameters:
        #======================
        #effective_returns: array like daily sharp ratio

        #returns:
        #=================
        #array-like array of weights

        #prepare the given target return seriers

        tvols=[]

        num_of_assets = len(self.symbols)
        initial_weights = []
        for i in range(0, num_of_assets):
            initial_weights.append(1. / num_of_assets)

        for tret in effective_returns:
            cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
                    {'type': 'eq', 'fun': lambda x: self.get_portfolio_statistics(x)[0] - tret})
            bnds = tuple((0, 1) for x in range(num_of_assets))
            opts = sco.minimize(self.__min_func_port, initial_weights, method='SLSQP', bounds=bnds,
                                constraints=cons)
            tvols.append(opts['fun'])
            # print(opts)

        return np.array(tvols)

    def get_effective_capital_market_line(self):
        "'draw effetive captial market line'"
        trets = np.linspace(0.0, 0.25, 50)
        tvols = self.get_weight_with_effective_returns(trets)

        ind = np.argmin(tvols)
        evols = tvols[ind:]
        erets = trets[ind:]
        tick = sci.splrep(evols, erets)

        def f(x):
            "' Efficient frontier function (splines approximation)'"
            return sci.splev(x, tick, der=0)

        def df(x):
            "' First derivative of efficient frontier function'"
            return sci.splev(x, tick, der=1)

        def equations(p, rf=self.risk_free_rate):
            eq1 = rf - p[0]
            eq2 = rf + p[1] * p[2] - f(p[2])
            eq3 = p[1] - df(p[2])
            return eq1, eq2, eq3

        opt = sco.fsolve(equations, [0.01, 0.5, 0.15])
        return opt

    def draw_capital_market_line_scatter_chart(self,risk_free_rate):
        "'draw effeive market line '"
        trets = np.linspace(0.0,0.25,50)
        tvols = self.get_vols_with_effective_returns(trets)
        print(tvols)





